package com.ccx.demo.config;

import com.utils.IAutoTask;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;

import java.util.LinkedHashMap;
import java.util.Optional;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 定时任务配置
 *
 * @author 谢长春 on 2018/1/5.
 */
@Slf4j
@Configuration
@EnableScheduling
@RequiredArgsConstructor
@ConditionalOnProperty(value = "spring.app.auto-task.enabled", havingValue = "true")
@ConfigurationProperties(prefix = "spring.app.auto-task")
public class ScheduleConfiguration implements SchedulingConfigurer {
    private static final int CORE_POOL_SIZE = 20;
    @Setter
    @Getter
    private LinkedHashMap<String, IAutoTask.AutoTaskItem> services;

    private final ApplicationContext applicationContext;
    private final RedissonClient redissonClient;

    /**
     * 方案一：支持 CronTrigger 表达式
     * 多线程定时任务调度器
     *
     * @return ThreadPoolTaskScheduler
     */
    @Bean(destroyMethod = "destroy")
    public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
        final ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(CORE_POOL_SIZE);
//        scheduler.setThreadNamePrefix("taskExecutor-");
//        scheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//        //调度器shutdown被调用时等待当前被调度的任务完成
//        scheduler.setWaitForTasksToCompleteOnShutdown(true);
//        //等待时长
//        scheduler.setAwaitTerminationSeconds(60);
        return scheduler;
    }

    /**
     * 方案二：仅支持时间戳
     * 多线程定时任务调度器
     *
     * @return ScheduledExecutorService
     */
    @Bean(destroyMethod = "shutdownNow")
    public ScheduledExecutorService scheduledExecutorService() {
        return Executors.newScheduledThreadPool(CORE_POOL_SIZE);
    }

    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        services.forEach((key, task) -> {
            final String beanName = Optional.ofNullable(task.getName()).orElse(key);
            if (!task.isEnabled()) {
                log.info("定时任务服务开关已关闭: {} : {} : {}", beanName, task.getCron(), task.getComment());
                return;
            }
            log.info("注册定时任务服务: {} : {} : {}", beanName, task.getCron(), task.getComment());
            scheduledTaskRegistrar.addCronTask(() -> {
                // 判断分布式锁开关是否打开
                if (task.isLockEnabled()) {
                    final RLock lock = redissonClient.getLock(beanName);
                    try {
                        // 锁争夺成功之后，不立即释放锁，等待 redis 锁过期，避免服务器时间差，造成重复执行
                        final boolean locked = lock.tryLock(
                                1L, // 加锁等待时间
                                task.getLockExpired().getSeconds(), // 锁过期时间， 到期自动解锁，不需要使用代码解锁
                                TimeUnit.SECONDS
                        );
                        if (!locked) {
                            throw new IllegalStateException("locked = false");
                        }
                    } catch (Exception e) {
                        // 锁争夺失败， 不执行业务逻辑，直接退出
                        log.info("锁争夺失败: {} : {} : {}", beanName, task.getComment(), e.getMessage());
                        return;
                    }
                }
                try {
                    if (log.isInfoEnabled()) {
                        log.info("开始:{}", task.getComment());
                    }
                    applicationContext.getBean(beanName, IAutoTask.class).call(null);
                } catch (Exception e) {
                    log.error("异常：{}", task.getComment(), e);
                } finally {
                    log.info("结束:{}", task.getComment());
                }
            }, task.getCron());

            if (task.isStarted()) {
                // 判断分布式锁开关是否打开
                if (task.isLockEnabled()) {
                    final RLock lock = redissonClient.getLock(beanName);
                    try {
                        // 锁争夺成功之后，不立即释放锁，等待 redis 锁过期，避免服务器时间差，造成重复执行
                        final boolean locked = lock.tryLock(
                                1L, // 加锁等待时间
                                task.getLockExpired().getSeconds(), // 锁过期时间， 到期自动解锁，不需要使用代码解锁
                                TimeUnit.SECONDS
                        );
                        if (!locked) {
                            throw new IllegalStateException("locked = false");
                        }
                    } catch (Exception e) {
                        // 锁争夺失败， 不执行业务逻辑，直接退出
                        log.info("锁争夺失败: {} : {} : {}", beanName, task.getComment(), e.getMessage());
                        return;
                    }
                }
                // 启动时先执行一次， ScheduledExecutorService 添加延时功能，保证服务启动成功之后再执行定时任务，否则在 bean 初始化时执行，会出现报错
                scheduledExecutorService().schedule(
                        () -> applicationContext.getBean(beanName, IAutoTask.class).started(),
                        1,
                        TimeUnit.SECONDS
                );
            }
        });
        scheduledTaskRegistrar.setScheduler(threadPoolTaskScheduler());
    }

}
